﻿namespace SenenFernandez.SmartExtensionMethods
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Threading;
    using System.Windows;
    using System.Windows.Markup;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Media.Media3D;
    using System.Windows.Threading;

    /// <summary>
    /// Extension Methods for Windows Presentation Foundation.
    /// </summary>
    public static class Wpf
    {
        /// <summary>
        /// Sets current culture.
        /// </summary>
        public static void SetCulture(this string name)
        {
            var culture = new CultureInfo(name);
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = culture;
            FrameworkElement.LanguageProperty.OverrideMetadata(typeof (FrameworkElement),
                new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));
        }

        /// <summary>
        /// Gets the culture name by Country name.
        /// </summary>
        /// <param name="countryName"></param>
        /// <returns></returns>
        public static string GetCountryCulture(this string countryName)
        {
            var culture = CultureInfo.GetCultures(CultureTypes.AllCultures)
                .Where(source => source.DisplayName.ToUpperOrEmpty().Contains(countryName.ToUpperOrEmpty()))
                .FirstOrDefault();
            return culture != null ? culture.Name : CultureInfo.CurrentCulture.Name;
        }

        /// <summary>
        /// Gets an ImageSource from byte[]
        /// </summary>
        public static BitmapImage ToBitmapImage(this byte[] value)
        {
            var image = new BitmapImage();
            if (value != null)
            {
                using (var stream = new MemoryStream(value))
                {
                    image.BeginInit();
                    image.StreamSource = stream;
                    image.EndInit();
                    image.Freeze();
                }
            }
            return image;
        }

        /// <summary>
        /// Gets an ImageSource from string path
        /// </summary>
        public static ImageSource ToImageSource(this string value)
        {
            ImageSource imageSource = null;
            if (!string.IsNullOrEmpty(value))
            {
                imageSource = new BitmapImage(new Uri(value, UriKind.RelativeOrAbsolute));
            }
            return imageSource;
        }

        /// <summary>
        /// FindResource by name.
        /// </summary>
        public static T FindResource<T>(this string name)
        {
            var result = Application.Current.TryFindResource(name);
            return (T) (result ?? default(T));
        }

        /// <summary>
        /// Gets the Style from name.
        /// </summary>
        public static Style GetStyle(this string name)
        {
            var result = default(Style);
            if (Application.Current.Resources.Contains(name))
            {
                result = (Style)Application.Current.Resources[name];
            }
            return result;
        }

        /// <summary>
        /// InvokeAsync
        /// </summary>
        public static void InvokeAsync(this Action action)
        {
            if (Application.Current != null)
            {
                Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, action);
            }
        }

        /// <summary>
        /// Invert a given color.
        /// </summary>
        public static Color Invert(this Color color)
        {
            var invertedColor = new Color
            {
                ScR = 1.0F - color.ScR,
                ScG = 1.0F - color.ScG,
                ScB = 1.0F - color.ScB,
                ScA = color.ScA
            };
            return invertedColor;
        }

        /// <summary>
        /// Converts the current color to Brush.
        /// </summary>
        public static SolidColorBrush ToBrush(this Color color)
        {
            var brush = new SolidColorBrush(color);
            brush.Freeze();
            return brush;
        }

        /// <summary>
        /// Gets the first parent for the child.
        /// </summary>
        public static T GetFirstParentForChild<T>(this DependencyObject child) where T : class
        {
            if (child == null)
            {
                return null;
            }
            var candidate = child as T;
            return candidate ?? GetFirstParentForChild<T>(VisualTreeHelper.GetParent(child));
        }

        /// <summary>
        /// Gets the VisualParent of the element.
        /// </summary>
        public static T FindVisualParent<T>(UIElement element) where T : UIElement
        {
            var parent = element;
            while (parent != null)
            {
                var correctlyTyped = parent as T;
                if (correctlyTyped != null)
                {
                    return correctlyTyped;
                }
                parent = VisualTreeHelper.GetParent(parent) as UIElement;
            }
            return null;
        }

        /// <summary>
        /// Find the first child of the specified type (the child must exist)
        /// by walking down the logical/visual trees
        /// Will throw an exception if a matching child does not exist. If you're not sure, use the TryFindChild method instead.
        /// </summary>
        /// <typeparam name="T">The type of child you want to find</typeparam>
        /// <param name="parent">The dependency object whose children you wish to scan</param>
        /// <returns>The first descendant of the specified type</returns>
        /// <remarks> usage: myWindow.FindChild<StackPanel>() </StackPanel></remarks>
        public static T FindChild<T>(this DependencyObject parent)
            where T : DependencyObject
        {
            return parent.FindChild<T>(child => true);
        }

        /// <summary>
        /// Find the first child of the specified type (the child must exist)
        /// by walking down the logical/visual trees, which meets the specified criteria
        /// Will throw an exception if a matching child does not exist. If you're not sure, use the TryFindChild method instead.
        /// </summary>
        /// <typeparam name="T">The type of child you want to find</typeparam>
        /// <param name="parent">The dependency object whose children you wish to scan</param>
        /// <param name="predicate">The child object is selected if the predicate evaluates to true</param>
        /// <returns>The first matching descendant of the specified type</returns>
        /// <remarks> usage: myWindow.FindChild<StackPanel>( child => child.Name == "myPanel" ) </StackPanel></remarks>
        public static T FindChild<T>(this DependencyObject parent, Func<T, bool> predicate)
            where T : DependencyObject
        {
            return parent.FindChildren(predicate).First();
        }

        /// <summary>
        /// Use this overload if the child you're looking may not exist.
        /// </summary>
        /// <typeparam name="T">The type of child you're looking for</typeparam>
        /// <param name="parent">The dependency object whose children you wish to scan</param>
        /// <param name="foundChild">out param - the found child dependencyobject, null if the method returns false</param>
        /// <returns>True if a child was found, else false</returns>
        public static bool TryFindChild<T>(this DependencyObject parent, out T foundChild)
            where T : DependencyObject
        {
            return parent.TryFindChild(child => true, out foundChild);
        }

        /// <summary>
        /// Use this overload if the child you're looking may not exist.
        /// </summary>
        /// <typeparam name="T">The type of child you're looking for</typeparam>
        /// <param name="parent">The dependency object whose children you wish to scan</param>
        /// <param name="predicate">The child object is selected if the predicate evaluates to true</param>
        /// <param name="foundChild">out param - the found child dependencyobject, null if the method returns false</param>
        /// <returns>True if a child was found, else false</returns>
        public static bool TryFindChild<T>(this DependencyObject parent, Func<T, bool> predicate, out T foundChild)
            where T : DependencyObject
        {
            foundChild = null;
            var results = parent.FindChildren(predicate).ToList();
            if (results.Count == 0)
            {
                return false;
            }
            foundChild = results.First();
            return true;
        }

        /// <summary>
        /// Get a list of descendant dependencyobjects of the specified type and which meet the criteria
        /// as specified by the predicate
        /// </summary>
        /// <typeparam name="T">The type of child you want to find</typeparam>
        /// <param name="parent">The dependency object whose children you wish to scan</param>
        /// <param name="predicate">The child object is selected if the predicate evaluates to true</param>
        /// <returns>The first matching descendant of the specified type</returns>
        /// <remarks> usage: myWindow.FindChildren<StackPanel>( child => child.Name == "myPanel" ) </StackPanel></remarks>
        public static IEnumerable<T> FindChildren<T>(this DependencyObject parent, Func<T, bool> predicate)
            where T : DependencyObject
        {
            var children = new List<DependencyObject>();

            if ((parent is Visual) || (parent is Visual3D))
            {
                var visualChildrenCount = VisualTreeHelper.GetChildrenCount(parent);
                for (int childIndex = 0; childIndex < visualChildrenCount; childIndex++)
                {
                    children.Add(VisualTreeHelper.GetChild(parent, childIndex));
                }
            }
            foreach (var logicalChild in LogicalTreeHelper.GetChildren(parent).OfType<DependencyObject>())
            {
                if (!children.Contains(logicalChild))
                {
                    children.Add(logicalChild);
                }
            }
            foreach (var child in children)
            {
                var typedChild = child as T;
                if ((typedChild != null) && predicate.Invoke(typedChild))
                {
                    yield return typedChild;
                }
                foreach (var foundDescendant in FindChildren(child, predicate))
                {
                    yield return foundDescendant;
                }
            }
            yield break;
        }

        /// <summary>
        /// Converts System.Windows.Media.Color To int representation.
        /// </summary>
        public static int ToInt(this Color color)
        {
            var bytes = new byte[] { color.A, color.R, color.G, color.B };
            return BitConverter.ToInt32(bytes, 0);
        }

        /// <summary>
        /// Converts int number to System.Windows.Media.Brush representation.
        /// </summary>
        public static Brush ToBrush(this int color)
        {
            var bytes = BitConverter.GetBytes(color);
            var brush = new SolidColorBrush(Color.FromArgb(bytes[0], bytes[1], bytes[2], bytes[3]));
            brush.Freeze();
            return brush;
        }

        /// <summary>
        /// Maximizes a window to a specific display monitor.
        /// </summary>
        public static bool Maximize(this Window window, string displayName = null)
        {
            bool result = false;
            var monitor = displayName.GetMonitorOrDefault();
            if (monitor != null)
            {
                var workingArea = monitor.WorkingArea;
                window.Left = workingArea.Left;
                window.Top = workingArea.Top;
                window.Width = workingArea.Width;
                window.Height = workingArea.Height;
                result = true;
            }
            return result;
        }

        public static string GetPropertyName<T>(this Expression<Func<T>> propertyExpression)
        {
            string result = default(string);
            if (propertyExpression != null)
            {
                var memberExpression = propertyExpression.Body as MemberExpression;
                result = memberExpression.Member.Name;
            }
            return result;
        }
    }
}